异步解决方案的发展历程
1. 回调函数
缺点:回调地狱,不能用 try catch 捕获错误,不能 return
2. Promise
Promise 是为了解决回调地狱的问题而产生的。
优点:解决了回调地狱的问题 缺点:无法取消 Promise ,错误需要通过回调函数来捕获
3. generator
特点:可以控制函数的执行,可以配合 co 函数库使用
function* fetch() {
yield ajax("XXX1", () => {});
yield ajax("XXX2", () => {});
yield ajax("XXX3", () => {});
}
let it = fetch();
let result1 = it.next();
let result2 = it.next();
let result3 = it.next();
4. async/await
async、await 是异步的终极解决方案
优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题 缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
Promise
Promise 有以下两个特点:
- 对象的状态不受外界影响。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise 构造函数是同步执行,then 方法是异步执行。
let promise = new Promise(function (resolve, reject) {
console.log("Promise");
resolve();
});
promise.then(function () {
console.log("Fulfilled.");
});
console.log("Hi!");
// Promise
// Hi!
// Fulfilled
优点:
- 避免可读性极差的回调地狱。
- 使用
.then()编写的顺序异步代码,既简单又易读。 - 使用
Promise.all()编写并行异步代码变得很容易。 - 代码结构更加扁平化,易读易理解,更加清晰明了。
- 可以更好的捕捉错误
缺点:
- 在不支持 ES6 的旧版浏览器中,需要引入 polyfill 才能使用。
- 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
- 当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
错误冒泡
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。Promise 会吃掉内部的错误,并不影响外部代码的运行。所以需要 catch,以防丢掉错误信息。
const someAsyncThing = function () {
return new Promise(function (resolve, reject) {
// 下面一行会报错,因为 x 没有声明
resolve(x + 2);
});
};
someAsyncThing()
.then(function () {
return someOtherAsyncThing();
})
.catch(function (error) {
console.error("oh no", error);
// 下面一行会报错,因为 y 没有声明
y + 2;
})
.catch(function (error) {
console.error("carry on", error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]
Promise 中 .then 的第二参数与 .catch 有什么区别?
Promise/A+ 规范,Promise 中的异常会被 then 的第二个参数作为参数传入。
主要区别就是:
- 如果在 then 的第一个函数里抛出了异常,后面的 catch 能捕获到,而 then 的第二个函数捕获不到。
- then 的第二个参数和 catch 捕获错误信息的时候会就近原则,如果是 promise 内部报错,reject 抛出错误后,then 的第二个参数和 catch 方法都存在的情况下,只有 then 的第二个参数能捕获到,如果 then 的第二个参数不存在,则 catch 方法会捕获到。
Promise.then 里抛出的错误能否被 try...catch 捕获,为什么。
因为 try catch 只能处理同步的错误,对异步错误没有办法捕获
什么时候 promise 不会被销毁?
类似问题:
- promise 如果没有 resolve 会怎么样?
- 什么情况会发生内存泄漏?
结论:
- 当一个 Promise 没有被赋值给一个变量,同时没调用 resolve、reject,那么它就会被垃圾回收。
- 如果每次都生成新的 new Promise 且 resolve 没有被调用,那么就直接被回收。
- 如果每次都生成新的 new Promise 但是 resolve 有被调用,但是一直没被执行,这个时候 Promise 就不会被垃圾回收。
- 如果使用同一个 new Promise ,且没有 resolve,那么也不会被垃圾回收。多次引用
破坏 promise 链
在一个 promise 链中,只要任何一个 promise 被 reject,promise 链就被破坏了,reject 之后的 promise 都不会再执行,而是直接调用 .catch 方法。这也是为什么在 standard practice 中,一定要在最后加上 .catch 的原因。通过 .catch 能够清楚的判断出 promise 链在哪个环节出了问题。
async/await
async 是 ES2017 标准推出的用于处理异步操作的关键字,async/await 做的事情就是将 Generator 函数转换成 Promise。
async 函数内阻塞,函数外不阻塞。async 函数可以保留运行堆栈。
async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
var a = 0;
var b = async () => {
a = a + (await 10);
console.log("2", a); // -> '2' 10
a = (await 10) + a;
console.log("3", a); // -> '3' 20
};
b();
a++;
console.log("1", a); // -> '1' 1
// 1 1
// 2 10
// 3 20
对于以上代码你可能会有疑惑,这里说明下原理
- 首先函数
b先执行,在执行到await 10之前变量a还是 0,因为在await内部实现了generators,generators会保留堆栈中东西,所以这时候a = 0被保存了下来 - 因为
await是异步操作,遇到await就会立即返回一个pending状态的Promise对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,所以会先执行console.log('1', a) - 这时候同步代码执行完毕,开始执行异步代码,将保存下来的值拿出来使用,这时候
a = 10 - 然后后面就是常规执行代码了